(import
 (except (rnrs base) let-values map error)
 (only (guile)
       lambda* λ
       string-split
       string-append
       string-join
       string-trim
       string-trim-right
       string-prefix?)
 (fileio)
 (srfi srfi-1)
 ;; hash tables
 (srfi srfi-69)
 (ice-9 pretty-print))


(define cd-in?
  (λ (str)
    (and (string-prefix? "$ cd" str)
         (not (cd-out? str)))))

(define cd-in-dir-name
  (λ (str)
    (string-join (drop (string-split str #\space) 2) " ")))

(define cd-out?
  (λ (str)
    (string=? "$ cd .." str)))

(define ls?
  (λ (str)
    (string=? "$ ls" str)))


(define dir?
  (λ (str)
    (string-prefix? "dir" str)))


(define file?
  (λ (str)
    (let ([parts (string-split str #\space)])
      (and (= (length parts) 2)
           (number? (string->number (first parts)))))))


(define file-size
  (λ (str)
    (string->number (first (string-split str #\space)))))


(define lines (get-lines-from-file "input"))

(define limit 100000)

(define lines-filtered
  (filter (λ (line)
            (or (cd-out? line)
                (cd-in? line)
                (file? line)))
          lines))

(define dir-size-map (make-hash-table equal?))

(let iter ([lines° lines-filtered] [dir-parts° '()])
  (cond
   [(null? lines°) dir-size-map]
   [else
    (let ([line (first lines°)])
      (cond
       [(cd-in? line)
        ;; Set size of new directory initially to zero.
        ;; (display
        ;;  (simple-format
        ;;   #f "entering new dir: ~a\n"
        ;;   (cons (cd-in-dir-name line)
        ;;         dir-parts°)))
        (hash-table-set! dir-size-map
                         (cons (cd-in-dir-name line)
                               dir-parts°)
                         0)
        ;; Step in by adding directory name to the directory
        ;; parts.
        (iter (drop lines° 1)
              (cons (cd-in-dir-name line)
                    dir-parts°))]
       ;; Stepping out of a directory merely changes the
       ;; directory parts, dropping 1 part.
       [(cd-out? line)
        (iter (drop lines° 1)
              (drop dir-parts° 1))]
       ;; If we see a file, add it to the current
       ;; directory's size and all parents' sizes.
       [(file? line)
        (let ([size (file-size line)])
          (let update-dir-sizes ([dir-parts°° dir-parts°])
            (unless (null? dir-parts°°)
              ;; (display
              ;;  (simple-format
              ;;   #f "updating size of ~a with ~a\n"
              ;;   dir-parts°° size))
              (hash-table-update! dir-size-map
                                  dir-parts°°
                                  (λ (old-val) (+ old-val size))
                                  (λ () size))
              (update-dir-sizes (drop dir-parts°° 1)))))
        (iter (drop lines° 1) dir-parts°)]
       ;; Skip other lines.
       [else
        (iter (drop lines° 1)
              dir-parts°)]))]))

;; (pretty-print
;;  (filter (λ (key) (= (length key) 1))
;;          (hash-table-keys dir-size-map)))

;; (define dirs
;;   (map (λ (alist-entry)
;;          (cons (string-join (reverse (car alist-entry)) "/")
;;                (cdr alist-entry)))
;;        (hash-table->alist dir-size-map)))

;; (pretty-print dirs)

(define filesystem-size 70000000)
(define filesystem-used
  (hash-table-ref dir-size-map '("/")))
(define filesystem-needed-space 30000000)
(define filesystem-must-free-space
  (- filesystem-needed-space (- filesystem-size filesystem-used)))

(display
 (simple-format
  #f "filesystem-size: ~a\n" filesystem-size))

(display
 (simple-format
  #f "filesystem-used: ~a\n" filesystem-used))

(display
 (simple-format
  #f "must free space: ~a\n" filesystem-must-free-space))

(display
 (simple-format
  #f "sum size under ~a: ~a\n" limit
  (hash-table-fold dir-size-map
                   (λ (key value previous)
                     (cond
                      [(<= value limit)
                       (+ previous value)]
                      [else
                       previous]))
                   0)))

(display
 (simple-format
  #f "size of smallest dir for freeing sufficient space: ~a\n"
  (apply min
         (filter (λ (size)
                   (>= size filesystem-must-free-space))
                 (hash-table-values dir-size-map)))))
